/**
 * The MOSES project, model based software engineering of networked embedded systems.
 * 
 * Copyright (C) 2010-2013, University of Applied Sciences Wildau,
 * Telematics Research Group, http://www.tm.th-wildau.de/research-group
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>.
 * 
 * Contributors:
 *     University of Applied Sciences Wildau - initial API and implementation
 * 
 * If you are interested in a commercial license please contact us via
 * e-mail: ralf.vandenhouten@th-wildau.de.
 */
package de.thwildau.tm.moses.abnf.annotations;

import com.google.common.base.Objects;
import com.google.common.collect.Iterators;
import de.thwildau.tm.moses.abnf.abnf.Rule;
import de.thwildau.tm.moses.abnf.annotations.AbnfData;
import de.thwildau.tm.moses.common.util.ResourcesUtil;
import de.thwildau.tm.moses.xabnf.xabnf.GeneratorModel;
import de.thwildau.tm.moses.xabnf.xabnf.RuleBinding;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.lang.reflect.Method;
import java.util.Iterator;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.xtend.core.macro.declaration.CompilationUnitImpl;
import org.eclipse.xtend.core.xtend.XtendFile;
import org.eclipse.xtend.lib.macro.AbstractClassProcessor;
import org.eclipse.xtend.lib.macro.TransformationContext;
import org.eclipse.xtend.lib.macro.declaration.AnnotationTypeDeclaration;
import org.eclipse.xtend.lib.macro.declaration.CompilationStrategy;
import org.eclipse.xtend.lib.macro.declaration.CompilationStrategy.CompilationContext;
import org.eclipse.xtend.lib.macro.declaration.CompilationUnit;
import org.eclipse.xtend.lib.macro.declaration.MutableAnnotationReference;
import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration;
import org.eclipse.xtend.lib.macro.declaration.MutableMethodDeclaration;
import org.eclipse.xtend.lib.macro.declaration.Type;
import org.eclipse.xtend.lib.macro.declaration.TypeReference;
import org.eclipse.xtend2.lib.StringConcatenation;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.xbase.lib.Exceptions;
import org.eclipse.xtext.xbase.lib.Extension;
import org.eclipse.xtext.xbase.lib.Functions.Function1;
import org.eclipse.xtext.xbase.lib.IteratorExtensions;
import org.eclipse.xtext.xbase.lib.Procedures.Procedure1;

/**
 * Class-Processor class for Active-Annotation <code>AbnfData</code>.
 * 
 * FIXME This class cannot properly load the EMF classes due to a class loading problem.
 * Possible solutions can be found under
 * 	- http://www.eclipsezone.com/eclipse/forums/t62908.html
 *  - http://www.eclipse.org/forums/index.php/m/989231
 */
@SuppressWarnings("all")
public class AbnfDataProcessor extends AbstractClassProcessor {
  public void doTransform(final MutableClassDeclaration clazz, @Extension final TransformationContext context) {
    try {
      final String file = this.getFileName(clazz);
      boolean _or = false;
      boolean _equals = Objects.equal(file, null);
      if (_equals) {
        _or = true;
      } else {
        boolean _isEmpty = file.isEmpty();
        _or = (_equals || _isEmpty);
      }
      if (_or) {
        context.addError(clazz, "@AbnfData: no file attribute given");
      } else {
        boolean _endsWith = file.endsWith(".xabnf");
        boolean _not = (!_endsWith);
        if (_not) {
          String _plus = ("@AbnfData: file \'" + file);
          String _plus_1 = (_plus + "\' is not a X-ABNF file.");
          context.addError(clazz, _plus_1);
        } else {
          CompilationUnit _compilationUnit = clazz.getCompilationUnit();
          final XtendFile xtendFile = ((CompilationUnitImpl) _compilationUnit).getXtendFile();
          ResourcesUtil _resourcesUtil = new ResourcesUtil();
          Resource _eResource = xtendFile.eResource();
          URI _uRI = _eResource.getURI();
          final URI fileUri = _resourcesUtil.findResource(_uRI, file);
          boolean _equals_1 = Objects.equal(fileUri, null);
          if (_equals_1) {
            String _plus_2 = ("@AbnfData: file \'" + file);
            final String message = (_plus_2 + "\' cannot be found. It must be located in the same package as the source file.");
            context.addError(clazz, message);
          } else {
            Resource _eResource_1 = xtendFile.eResource();
            final ResourceSet resourceSet = _eResource_1.getResourceSet();
            final Resource xabnfResource = resourceSet.getResource(fileUri, true);
            TreeIterator<EObject> _allContents = xabnfResource.getAllContents();
            final Function1<EObject,Boolean> _function = new Function1<EObject,Boolean>() {
                public Boolean apply(final EObject ele) {
                  boolean _xblockexpression = false;
                  {
                    EClass _eClass = ele.eClass();
                    final String eClassName = _eClass.getInstanceClassName();
                    final String genModelName = GeneratorModel.class.getName();
                    final boolean bool = eClassName.equals(genModelName);
                    String _plus = ("X-ABNF: " + ele);
                    String _plus_1 = (_plus + " name=");
                    String _plus_2 = (_plus_1 + eClassName);
                    String _plus_3 = (_plus_2 + " genModelName=");
                    String _plus_4 = (_plus_3 + genModelName);
                    String _plus_5 = (_plus_4 + " bool=");
                    String _plus_6 = (_plus_5 + Boolean.valueOf(bool));
                    System.out.println(_plus_6);
                    _xblockexpression = (bool);
                  }
                  return Boolean.valueOf(_xblockexpression);
                }
              };
            final EObject genModel = IteratorExtensions.<EObject>findFirst(_allContents, _function);
            final Rule entryRule = ((GeneratorModel) genModel).getEntryRule();
            final String packageName = ((GeneratorModel) genModel).getPackage();
            boolean _or_1 = false;
            boolean _equals_2 = Objects.equal(entryRule, null);
            if (_equals_2) {
              _or_1 = true;
            } else {
              boolean _equals_3 = Objects.equal(packageName, null);
              _or_1 = (_equals_2 || _equals_3);
            }
            if (_or_1) {
              String _plus_3 = ("@AbnfData: x-abnf file " + file);
              String _plus_4 = (_plus_3 + ": entryRule or packageName are not defined.");
              context.addWarning(clazz, _plus_4);
            } else {
              TreeIterator<EObject> _allContents_1 = xabnfResource.getAllContents();
              Iterator<RuleBinding> _filter = Iterators.<RuleBinding>filter(_allContents_1, RuleBinding.class);
              final Function1<RuleBinding,Boolean> _function_1 = new Function1<RuleBinding,Boolean>() {
                  public Boolean apply(final RuleBinding rb) {
                    Rule _rule = rb.getRule();
                    boolean _equals = _rule.equals(entryRule);
                    return Boolean.valueOf(_equals);
                  }
                };
              final RuleBinding ruleBinding = IteratorExtensions.<RuleBinding>findFirst(_filter, _function_1);
              boolean _equals_4 = Objects.equal(ruleBinding, null);
              if (_equals_4) {
                String _name = entryRule.getName();
                String _plus_5 = ("RuleBinding for " + _name);
                String _plus_6 = (_plus_5 + " not found.");
                context.addWarning(clazz, _plus_6);
              } else {
                final String targetRuleName = ruleBinding.getTargetRuleName();
                QualifiedName _create = QualifiedName.create(packageName);
                final QualifiedName fqn = _create.append(targetRuleName);
                try {
                  Class<? extends AbnfDataProcessor> _class = this.getClass();
                  ClassLoader _classLoader = _class.getClassLoader();
                  String _string = fqn.toString();
                  final Class<? extends Object> loadClass = _classLoader.loadClass(_string);
                  final Method method = loadClass.getMethod("readObject", DataInput.class);
                  final String content = this.getData(clazz);
                  boolean _or_2 = false;
                  boolean _equals_5 = Objects.equal(content, null);
                  if (_equals_5) {
                    _or_2 = true;
                  } else {
                    boolean _isEmpty_1 = content.isEmpty();
                    _or_2 = (_equals_5 || _isEmpty_1);
                  }
                  if (_or_2) {
                    context.addError(clazz, "@AbnfData: data is null or empty");
                  } else {
                    byte[] _bytes = content.getBytes();
                    ByteArrayInputStream _byteArrayInputStream = new ByteArrayInputStream(_bytes);
                    DataInputStream _dataInputStream = new DataInputStream(_byteArrayInputStream);
                    final DataInputStream dataInput = _dataInputStream;
                    final Object result = method.invoke(null, dataInput);
                    Class<? extends Object> _class_1 = result.getClass();
                    Method[] _methods = _class_1.getMethods();
                    for (final Method resultMethod : _methods) {
                      boolean _and = false;
                      String _name_1 = resultMethod.getName();
                      boolean _startsWith = _name_1.startsWith("get");
                      if (!_startsWith) {
                        _and = false;
                      } else {
                        String _name_2 = resultMethod.getName();
                        boolean _equals_6 = _name_2.equals("getClass");
                        boolean _not_1 = (!_equals_6);
                        _and = (_startsWith && _not_1);
                      }
                      if (_and) {
                        final Object internalResult = resultMethod.invoke(result);
                        final TypeReference typeReference = this.findTypeReference(resultMethod, context);
                        boolean _notEquals = (!Objects.equal(typeReference, null));
                        if (_notEquals) {
                          String _name_3 = resultMethod.getName();
                          final Procedure1<MutableMethodDeclaration> _function_2 = new Procedure1<MutableMethodDeclaration>() {
                              public void apply(final MutableMethodDeclaration it) {
                                it.setReturnType(typeReference);
                                boolean _isStringReturnType = AbnfDataProcessor.this.isStringReturnType(resultMethod);
                                if (_isStringReturnType) {
                                  final CompilationStrategy _function = new CompilationStrategy() {
                                      public CharSequence compile(final CompilationContext it) {
                                        StringConcatenation _builder = new StringConcatenation();
                                        _builder.append("return \"");
                                        _builder.append(internalResult, "");
                                        _builder.append("\";");
                                        return _builder;
                                      }
                                    };
                                  it.setBody(_function);
                                } else {
                                  boolean _isEnumReturnType = AbnfDataProcessor.this.isEnumReturnType(resultMethod);
                                  if (_isEnumReturnType) {
                                    final CompilationStrategy _function_1 = new CompilationStrategy() {
                                        public CharSequence compile(final CompilationContext it) {
                                          StringConcatenation _builder = new StringConcatenation();
                                          _builder.append("return ");
                                          _builder.append(typeReference, "");
                                          _builder.append(".");
                                          _builder.append(internalResult, "");
                                          _builder.append(";");
                                          return _builder;
                                        }
                                      };
                                    it.setBody(_function_1);
                                  } else {
                                    boolean _isFloatReturnType = AbnfDataProcessor.this.isFloatReturnType(resultMethod);
                                    if (_isFloatReturnType) {
                                      final CompilationStrategy _function_2 = new CompilationStrategy() {
                                          public CharSequence compile(final CompilationContext it) {
                                            StringConcatenation _builder = new StringConcatenation();
                                            _builder.append("return ");
                                            _builder.append(internalResult, "");
                                            _builder.append("f;");
                                            return _builder;
                                          }
                                        };
                                      it.setBody(_function_2);
                                    } else {
                                      final CompilationStrategy _function_3 = new CompilationStrategy() {
                                          public CharSequence compile(final CompilationContext it) {
                                            StringConcatenation _builder = new StringConcatenation();
                                            _builder.append("return ");
                                            _builder.append(internalResult, "");
                                            _builder.append(";");
                                            return _builder;
                                          }
                                        };
                                      it.setBody(_function_3);
                                    }
                                  }
                                }
                              }
                            };
                          clazz.addMethod(_name_3, _function_2);
                        }
                      }
                    }
                  }
                } catch (final Throwable _t) {
                  if (_t instanceof Exception) {
                    final Exception e = (Exception)_t;
                    e.printStackTrace();
                    Class<? extends Exception> _class_2 = e.getClass();
                    String _name_4 = _class_2.getName();
                    String _plus_7 = (_name_4 + ": ");
                    String _message = e.getMessage();
                    String _plus_8 = (_plus_7 + _message);
                    context.addError(clazz, _plus_8);
                  } else {
                    throw Exceptions.sneakyThrow(_t);
                  }
                }
              }
            }
          }
        }
      }
    } catch (Throwable _e) {
      throw Exceptions.sneakyThrow(_e);
    }
  }
  
  private String getFileName(final MutableClassDeclaration clazz) {
    MutableAnnotationReference _abnfDataAnnotation = this.getAbnfDataAnnotation(clazz);
    Object _value = _abnfDataAnnotation.getValue("file");
    return ((String) _value);
  }
  
  private String getData(final MutableClassDeclaration clazz) {
    MutableAnnotationReference _abnfDataAnnotation = this.getAbnfDataAnnotation(clazz);
    Object _value = _abnfDataAnnotation.getValue("data");
    return ((String) _value);
  }
  
  private MutableAnnotationReference getAbnfDataAnnotation(final MutableClassDeclaration clazz) {
    try {
      Iterable<? extends MutableAnnotationReference> _annotations = clazz.getAnnotations();
      for (final MutableAnnotationReference a : _annotations) {
        AnnotationTypeDeclaration _annotationTypeDeclaration = a.getAnnotationTypeDeclaration();
        String _qualifiedName = _annotationTypeDeclaration.getQualifiedName();
        String _name = AbnfData.class.getName();
        boolean _equals = _qualifiedName.equals(_name);
        if (_equals) {
          return a;
        }
      }
      String _name_1 = AbnfData.class.getName();
      String _plus = (_name_1 + " could not be determined.");
      IllegalAccessException _illegalAccessException = new IllegalAccessException(_plus);
      throw _illegalAccessException;
    } catch (Throwable _e) {
      throw Exceptions.sneakyThrow(_e);
    }
  }
  
  private boolean isStringReturnType(final Method method) {
    Class<? extends Object> _returnType = method.getReturnType();
    boolean _equals = _returnType.equals(String.class);
    return _equals;
  }
  
  private boolean isEnumReturnType(final Method method) {
    Class<? extends Object> _returnType = method.getReturnType();
    boolean _isEnum = _returnType.isEnum();
    return _isEnum;
  }
  
  private boolean isFloatReturnType(final Method method) {
    boolean _xblockexpression = false;
    {
      final Class<? extends Object> returnType = method.getReturnType();
      boolean _or = false;
      boolean _equals = returnType.equals(Float.class);
      if (_equals) {
        _or = true;
      } else {
        boolean _equals_1 = returnType.equals(Float.TYPE);
        _or = (_equals || _equals_1);
      }
      _xblockexpression = (_or);
    }
    return _xblockexpression;
  }
  
  private TypeReference findTypeReference(final Method method, @Extension final TransformationContext context) {
    TypeReference _xblockexpression = null;
    {
      final Class<? extends Object> returnType = method.getReturnType();
      boolean _matched = false;
      if (!_matched) {
        if (Objects.equal(returnType,String.class)) {
          _matched=true;
          context.getString();
        }
      }
      if (!_matched) {
        if (Objects.equal(returnType,Object.class)) {
          _matched=true;
          context.getObject();
        }
      }
      if (!_matched) {
        if (Objects.equal(returnType,Boolean.TYPE)) {
          _matched=true;
          context.getPrimitiveBoolean();
        }
      }
      if (!_matched) {
        if (Objects.equal(returnType,Integer.TYPE)) {
          _matched=true;
          context.getPrimitiveInt();
        }
      }
      if (!_matched) {
        if (Objects.equal(returnType,Float.TYPE)) {
          _matched=true;
          context.getPrimitiveFloat();
        }
      }
      if (!_matched) {
        if (Objects.equal(returnType,Double.TYPE)) {
          _matched=true;
          context.getPrimitiveFloat();
        }
      }
      if (!_matched) {
        if (Objects.equal(returnType,Long.TYPE)) {
          _matched=true;
          context.getPrimitiveLong();
        }
      }
      TypeReference _newTypeReference = null;
      Type _findTypeGlobally = context.findTypeGlobally(returnType);
      if (_findTypeGlobally!=null) {
        _newTypeReference=context.newTypeReference(_findTypeGlobally);
      }
      _xblockexpression = (_newTypeReference);
    }
    return _xblockexpression;
  }
  
  private void printClassLoader(final Class<? extends Object> clazz) {
    ClassLoader cl = clazz.getClassLoader();
    boolean _dowhile = false;
    do {
      {
        String _simpleName = clazz.getSimpleName();
        String _plus = (_simpleName + "-Classloader: ");
        String _plus_1 = (_plus + cl);
        System.out.println(_plus_1);
        ClassLoader _parent = cl.getParent();
        cl = _parent;
      }
      boolean _notEquals = (!Objects.equal(cl, null));
      _dowhile = _notEquals;
    } while(_dowhile);
  }
}
